/**
 * 实现js深拷贝
 */

 /**
  * 1.巧用JSON.stringify
  * 存在局限性： 无法处理function，无法处理正则，只有当对象是一个严格的json对象时，才可以顺利使用这个方法
  */
 const obj = {
     name: 'mzn',
     age: 28,
     habits: ['coding', 'hiking', 'running'],
     test: null
 }
 const str = JSON.stringify(obj);
 const objCopy = JSON.parse(str)
 obj.habits.splice(0,1);

 console.log('obj.habit', obj.habits)
 console.log('obj.habit', objCopy.habits)

 console.log('obj', obj)
 console.log('objCopy', objCopy)

 /**
  * 2. 递归实现深度拷贝：若为值类型，则直接返回，若为引用类型，则递归遍历
  * 1）参数的数据类型校验
  * 2）判断是否为对象逻辑是否严谨
  * 3）考虑数组的兼容
  * 4）ES6 其他数据结构map，set
  * 5） 爆栈——当数据的层次很深就会栈溢出 —— 重点
  * 6） 循环引用问题：循环检测、暴力破解 ——重点
  */
 function deepClone(obj) {
     // 如果是值类型，或者null，则返回
    if (typeof obj !== 'object' || obj === null) {
        return obj
    }
    let copy = {};

    // 类型为数组
    if (obj.constructor === Array) {
        copy = []
    }

    for(let key in obj) {
        if (obj.hasOwnProperty(key)) {
            copy[key] = deepClone(obj[key])
        }
    }

    return copy;
 }

 let _obj = {
     a: {
         b: {
             c:21,
             arr: [0,9]
         },
         d: 'mzn',
         arr: [7]
     },
     str: 'string',
 }

 let copy = deepClone(_obj);
 _obj.a.arr = [1];
 console.log('copy.a.arr', copy.a.arr)

/**
 * 爆栈问题
 * 校验爆栈情况：层级很深的话会栈溢出，当数据的广度不会造成溢出
 * @param {*} deep  深度
 * @param {*} breath 广度
 */
 function createData(deep, breath) {
     var temp = {};
     var data = temp;
     for(var i = 0;i < deep;i++) {
        temp = temp['data'] = {}
        for(var j = 0; j < breath;j++) {
            temp[j] = j
        }
     }
     return data;
 }
 console.log(createData(1,3))
 console.log(createData(3,0))

 deepClone(createData(1000)); // ok
// deepClone(createData(10000)); // Maximum call stack size exceeded 深度太深会爆栈
deepClone(createData(10, 100000)); // ok 广度不会溢出

/**
 *  循环引用问题
 */
var a = {};
a.a = a;
// deepClone(a) // 爆栈

function cloneJSON(source) {
    return JSON.parse(JSON.stringify(source));
}

// cloneJSON(createData(10000)) // 爆栈--- 也是用递归的方式
cloneJSON(a); // 并没有爆栈溢出，内部做了循环检测，Uncaught TypeError: Converting circular structure to JSON 

/**
 * 破解递归爆栈：使用循环遍历一颗树，需要借助一个栈
 */
function cloneLoop(x) {
    const root = {};
    // 栈
    const loopList = [{
        parent: root,
        key: undefined,
        data: x
    }];

    while(loopList.length) {
        // 深度优先
        const node = loopList.pop();
        const parent = node.parent;
        const key = node.key;
        const data = node.data;

        // 初始化赋值目标，key为undefined则拷贝到父元素，否则拷贝到子元素
        let res = parent;
        if (typeof key !== 'undefined') {
            res = parent[key] = {}
        }
        for (let k in data) {
            if (data.hasOwnProperty(k)) {
                if (typeof data[k] === 'object') {
                    // 下一次循环
                    loopList.push({
                        parent: res,
                        key: k,
                        data: data[k]
                    })
                }else {
                    res[k] = data[k];
                }
            }
        }
    }

    return root;
}

/**
 * 性能对比：影响性能两个因素-深度、广度
 */